# -*- coding: utf-8 -*-
import win32serviceutil
import win32service
import win32event
import signal
import traceback
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

'''
history:
    2015-8-7:  V1.0,初始版本
'''

VERSION = '1.0'
ATTEND_CTM_MS_DBG = True
ATTEND_IP_NET = ['192', '168', '13']
DEFAULT_IP_POOL = ['192.168.13.98', '192.168.13.99']
SERVICE_IP = '192.168.13.100'
SERVICE_MASK = '255.255.255.0'
MC_HOST = '0.0.0.0'
MC_PORT = 9013
#MC_DESTADDR = '224.13.1.1'
MC_DESTADDR = '192.168.13.255'
MC_TTL = 255
ATTEND_IS_MASTER = False
ATTEND_CTM_MASTER_IP = None
STARTUP_CMT = 60    # power startup choice master timeout
RUNNING_CMT = 5     # running state choice master timeout
IS_SIGINT_UP = False
SENDER_THREAD = True
INTERNET_FAILED = 0

def sigint_handler(signum, frame):
    global IS_SIGINT_UP
    IS_SIGINT_UP = True
    print 'catched interrupt signal!'

def get_network_1():
    import wmi
    c = wmi.WMI()
    intf = {}
    is_service = False
    for interface in c.Win32_NetworkAdapterConfiguration(IPEnabled=True):
        intf['name'] = interface
        intf['ip'] = []
        intf['mask'] = []
        for i, ip_address in enumerate(interface.IPAddress):
            intf['ip'].append(interface.IPAddress[i])
            intf['mask'].append(interface.IPSubnet[i])
        if set(intf.get('ip')).intersection(set(DEFAULT_IP_POOL)):
            return intf
        else:
            intf = {}
    return None

def get_network_2():
    import os
    import re
    ip_config_lines = os.popen('netsh interface ipv4 show address').readlines()
    intf = {}
    for cfg_line in ip_config_lines:
        cfg_line = cfg_line.strip().decode('gb2312')
        #print [cfg_line]
        if cfg_line == '' and intf and intf.get('ip') and intf.get('mask'):
            if set(intf.get('ip')).intersection(set(DEFAULT_IP_POOL)):
                return intf
            else:
                intf = {}
                continue
        m = re.match(u'接口 "([^"]+)" 的配置', cfg_line)
        if m:
            #print m.group(1)
            intf['name'] = m.group(1)
            continue
        m = re.match(u'IP 地址:\s*([\d\.]+)', cfg_line)
        if m:
            #print m.group(1)
            if not intf.get('ip'):
                intf['ip'] = []
            intf['ip'].append(m.group(1))
            continue
        m = re.match(u'.*\(掩码 ([\d\.]+)\)', cfg_line)
        if m:
            #print m.group(1)
            if not intf.get('mask'):
                intf['mask'] = []
            intf['mask'].append(m.group(1))
            continue
    return None

def proc_is_alive(processname):
    import os
    tasklist = os.popen('tasklist').read().split('\n')
    for task in tasklist:
        #print task
        if task and task.find(processname) >= 0:
            #print task
            return True
    return False

def port_is_open(ip, port):
    import socket
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1)
        s.connect((ip, int(port)))
        s.shutdown(2)
        return True
    except:
        return False

def check_internet():
    import urllib2
    try:
        httpcode = urllib2.urlopen('http://115.182.69.8', timeout=3).getcode()
        if httpcode == 200:
            return True
    except:
        return False
    return False

def get_ctm_info():
    global ATTEND_IS_MASTER
    global INTERNET_FAILED
    data = {}
    data['app_live'] = proc_is_alive("CardPoolView.exe")
    data['port_9100'] = port_is_open("127.0.0.1", 9100)
    data['port_3306'] = port_is_open("127.0.0.1", 3306)
    if check_internet():
        INTERNET_FAILED = 0
    else:
        INTERNET_FAILED = INTERNET_FAILED + 1
    data['internet'] = INTERNET_FAILED
    data['is_master'] = ATTEND_IS_MASTER
    return data


def attend_ip_set_1(intf, iplist, masklist):
    if ATTEND_CTM_MS_DBG:
        print 'set ip:', iplist, masklist
    ret = intf.EnableStatic(IPAddress=iplist, SubnetMask=masklist)
    if ret[0] == 0:
        print '[SET-IP] ok'
        return True
    elif ret[0] == 1:
        print '[SET-IP] ok(reboot)'
        return True
    else:
        print '[SET-IP] error'
        return False

def attend_ip_set_2(intf, iplist, masklist):
    import os
    addr_list = zip(iplist, masklist)
    for idx, addr in addr_list:
        if idx == 0:
            net_set_cmd = 'netsh interface ipv4 set address "%s" static %s %s' % (intf.encode('gb2312'), addr[0], addr[1])
        else:
            net_set_cmd = 'netsh interface ipv4 add address "%s" %s %s' % (intf.encode('gb2312'), addr[0], addr[1])
        os.popen(net_set_cmd)

def attend_ip_add(intf, ip, mask):
    import os
    net_cmd = 'netsh interface ipv4 add address "%s" address=%s mask=%s' % (intf.encode('gb2312'), ip, mask)
    ret = os.popen(net_cmd).read()
    print [net_cmd], ':', ret

def attend_ip_del(intf, ip):
    import os
    net_cmd = 'netsh interface ipv4 delete address "%s" address=%s' % (intf.encode('gb2312'), ip)
    ret = os.popen(net_cmd).read()
    print [net_cmd], ':', ret

def sender(data):
    import socket
    import struct
    global MC_DESTADDR
    global MC_PORT

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    #s.bind((MC_HOST, MC_PORT))
    # Set time-to-live (optional)
    #ttl_bin = struct.pack('@i', MC_TTL)
    #s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl_bin)
    #s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
    #        socket.inet_aton(MC_DESTADDR) + socket.inet_aton(MC_HOST))
    s.sendto(data, (MC_DESTADDR, MC_PORT))
    s.close()
    #print 'send data ok!'

def receiver(timeout):
    import socket
    import select
    import time
    global MC_DESTADDR
    global MC_PORT

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    s.bind((MC_HOST, MC_PORT))
    #s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
    #        socket.inet_aton(MC_DESTADDR) + socket.inet_aton(MC_HOST))
    s.setblocking(0)

    inputs = [s]
    outputs = []
    ctm_list = {}

    start_tm = int(time.time())
    #if ATTEND_CTM_MS_DBG:
    #    print 'start_tm:', start_tm

    while not IS_SIGINT_UP:
        end_tm = int(time.time())
        if end_tm - start_tm >= timeout:
            #if ATTEND_CTM_MS_DBG:
            #    print 'end_tm:', end_tm
            break
        readable, writable, exceptional = select.select(inputs, outputs, inputs, 1)
        if not (readable or writable or exceptional):
            #print "Time out ! "
            continue
        for rd in readable:
            if rd is s:
                data, addr = s.recvfrom(1024)
                print "Receive data!", time.time(), addr    #, data
                ctm_list[addr] = data
    s.close()
    '''
    [
    (('192.168.13.98', 1234),
        '{"port_3306": true, "port_9100": false, "app_live": false, "internet": 0, "is_master": true}'),
    (('192.168.13.99', 1234),
        '{"port_3306": true, "port_9100": false, "app_live": false, "internet": 0, "is_master": false}')
    ]
    '''
    return ctm_list.items()

def select_master(ctm_list):
    import json
    '''
    {
        '192.168.13.98':
            {
                "port_3306": True,
                "port_9100": False,
                "app_live": False,
                "internet": 0,
                "is_master": True
                "last_tm": 1440554929.092935
            },
        '192.168.13.99':
            {
                "port_3306": True,
                "port_9100": False,
                "app_live": False,
                "internet": 0,
                "is_master": False
                "last_tm": 1440554929.092935
            },
    }
    '''
    ATTEND_CTM_MS_DATA = {}
    if len(ctm_list) > 0:
        for ctm_info in ctm_list:
            ctm_ip = ctm_info[0][0]
            ctm_data = json.loads(ctm_info[1])
            ATTEND_CTM_MS_DATA[ctm_ip] = ctm_data
    ctm_ip_list = ATTEND_CTM_MS_DATA.keys()
    ctm_ip_list.sort()
    # return zhe old master ctm_ip
    #for ctm_ip in ctm_ip_list:
    #    if ATTEND_CTM_MS_DATA[ctm_ip]['is_master']:
    #        return ctm_ip
    # exclude port/app error ctm
    for ctm_ip in ctm_ip_list:
        if not ATTEND_CTM_MS_DATA[ctm_ip]['port_3306'] \
            or not ATTEND_CTM_MS_DATA[ctm_ip]['port_9100'] \
            or not ATTEND_CTM_MS_DATA[ctm_ip]['app_live']:
            ctm_ip_list.remove(ctm_ip)
    # add internet ok ctm to list
    ctm_internet_list = []
    for ctm_ip in ctm_ip_list:
        if ATTEND_CTM_MS_DATA[ctm_ip]['internet'] < 5:
            ctm_internet_list.append(ctm_ip)
    if len(ctm_internet_list) > 0:
        for ctm_ip in ctm_internet_list:
            if ATTEND_CTM_MS_DATA[ctm_ip]['is_master']:
                return ctm_ip
        return ctm_internet_list[0]
    for ctm_ip in ctm_ip_list:
        if ATTEND_CTM_MS_DATA[ctm_ip]['is_master']:
            return ctm_ip
    if len(ctm_ip_list) > 0:
        return ctm_ip_list[0]
    return None

def attend_info_send():
    import json
    import time
    global IS_SIGINT_UP
    global SENDER_THREAD
    while not IS_SIGINT_UP and SENDER_THREAD:
        info = json.dumps(get_ctm_info())
        if ATTEND_CTM_MS_DBG:
            print 'send-data, SENDER_THREAD:', SENDER_THREAD   #, info
        sender(info)
        time.sleep(1)

def attend_ms_run():
    import time
    from threading import Timer
    global ATTEND_IS_MASTER
    global ATTEND_CTM_MS_DBG
    global MC_HOST
    global SENDER_THREAD

    print 'VERSION:', VERSION
    intf = get_network_2()
    if not intf:
        print '[ERROR] not found avaliable interface'
        return

    if SERVICE_IP in intf.get('ip'):
        idx = intf['ip'].index(SERVICE_IP)
        intf['ip'].pop(idx)
        intf['mask'].pop(idx)
        #attend_ip_set_2(intf['name'], intf['ip'], intf['mask'])
        attend_ip_del(intf['name'], SERVICE_IP)
    if ATTEND_CTM_MS_DBG:
        print intf['name'], intf['ip'], intf['mask']

    SENDER_THREAD = True
    tjob = Timer(1, attend_info_send)
    tjob.start()

    master_ip = select_master(receiver(10))
    if ATTEND_CTM_MS_DBG:
        print 'master-ip:', master_ip
    if master_ip in intf['ip']:
        SENDER_THREAD = False
        tjob.join()
        ATTEND_IS_MASTER = True
        #intf['ip'].append(SERVICE_IP)
        #intf['mask'].append(SERVICE_MASK)
        #attend_ip_set_2(intf['name'], intf['ip'], intf['mask'])
        attend_ip_add(intf['name'], SERVICE_IP, SERVICE_MASK)
        if ATTEND_CTM_MS_DBG:
            print '[MASTER] is set', intf['name'], SERVICE_IP, SERVICE_MASK
        SENDER_THREAD = True
        tjob = Timer(1, attend_info_send)
        tjob.start()

    while not IS_SIGINT_UP:
        master_ip = select_master(receiver(5))
        if ATTEND_CTM_MS_DBG:
            print '[master-ip]:', master_ip, ATTEND_IS_MASTER
        if master_ip in intf['ip'] and not ATTEND_IS_MASTER:
            SENDER_THREAD = False
            tjob.join()
            ATTEND_IS_MASTER = True
            #intf['ip'].append(SERVICE_IP)
            #intf['mask'].append(SERVICE_MASK)
            #attend_ip_set_2(intf['name'], intf['ip'], intf['mask'])
            attend_ip_add(intf['name'], SERVICE_IP, SERVICE_MASK)
            if ATTEND_CTM_MS_DBG:
                print '[MASTER-SET]', intf['name'], SERVICE_IP, SERVICE_MASK
            SENDER_THREAD = True
            tjob = Timer(1, attend_info_send)
            tjob.start()
        if not master_ip in intf['ip'] and ATTEND_IS_MASTER:
            SENDER_THREAD = False
            tjob.join()
            ATTEND_IS_MASTER = False
            #idx = intf['ip'].index(SERVICE_IP)
            #intf['ip'].pop(idx)
            #intf['mask'].pop(idx)
            #attend_ip_set_2(intf['name'], intf['ip'], intf['mask'])
            attend_ip_del(intf['name'], SERVICE_IP)
            if ATTEND_CTM_MS_DBG:
                print '[MASTER-CLEAR]', intf['name'], intf['ip'], intf['mask']
            SENDER_THREAD = True
            tjob = Timer(1, attend_info_send)
            tjob.start()

class PythonService(win32serviceutil.ServiceFramework):
    """
    Usage: 'PythonService.py [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'
    Options for 'install' and 'update' commands only:
     --username domain\username : The Username the service is to run under
     --password password : The password for the username
     --startup [manual|auto|disabled|delayed] : How the service starts, default = manual
     --interactive : Allow the service to interact with the desktop.
     --perfmonini file: .ini file to use for registering performance monitor data
     --perfmondll file: .dll file to use when querying the service for
       performance data, default = perfmondata.dll
    Options for 'start' and 'stop' commands only:
     --wait seconds: Wait for the service to actually start or stop.
                     If you specify --wait with the 'stop' option, the service
                     and all dependent services will be stopped, each waiting
                     the specified period.
    """
    #服务名
    _svc_name_ = "AttendMSController"
    #服务显示名称
    _svc_display_name_ = "Attend Master/Slaver Controller"
    #服务描述
    _svc_description_ = "学而思电子考勤中控机主备控制"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        self.logger = self._getLogger()
        self.isAlive = True

    def _getLogger(self):
        import logging
        import os
        import inspect

        logger = logging.getLogger('[attend_ms_ctl]')

        this_file = inspect.getfile(inspect.currentframe())
        dirpath = os.path.abspath(os.path.dirname(this_file))
        handler = logging.FileHandler(os.path.join(dirpath, "attend_ms_ctl.log"))

        formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
        handler.setFormatter(formatter)

        logger.addHandler(handler)
        logger.setLevel(logging.INFO)

        return logger

    def SvcDoRun(self):
        import time
        self.logger.info("attend_ms_ctl do run....")
        while self.isAlive:
            self.logger.info("python attend_ms_ctl alive.")
            try:
                ret = attend_ms_run()
                self.logger.info(ret)
            except Exception as e:
                self.logger.error(str(e))
            time.sleep(300)
        # 等待服务被停止
        #win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)

    def SvcStop(self):
        # 先告诉SCM停止这个过程
        self.logger.info("python attend_ms_ctl do stop....")
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        # 设置事件
        win32event.SetEvent(self.hWaitStop)
        self.isAlive = False

if __name__=='__main__':
    #win32serviceutil.HandleCommandLine(PythonService)
    signal.signal(signal.SIGINT, sigint_handler)
    try:
        attend_ms_run()
    except Exception as e:
        print 'error:', str(e)
        traceback.print_exc()
